{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true,
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## 面向对象编程\n",
    "\n",
    "面向对象编程——Object Oriented Programming，简称OOP，是一种程序设计思想。OOP把对象作为程序的基本单元，一个对象包含了数据和操作数据的函数。\n",
    "\n",
    "面向过程的程序设计把计算机程序视为一系列的命令集合，即一组函数的顺序执行。为了简化程序设计，面向过程把函数继续切分为子函数，即把大块函数通过切割成小块函数来降低系统的复杂度。\n",
    "\n",
    "而面向对象的程序设计把计算机程序视为一组对象的集合，而每个对象都可以接收其他对象发过来的消息，并处理这些消息，计算机程序的执行就是一系列消息在各个对象之间传递。\n",
    "\n",
    "在Python中，所有数据类型都可以视为对象，当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类（Class）的概念。\n",
    "\n",
    "我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。\n",
    "\n",
    "假设我们要处理学生的成绩表，为了表示一个学生的成绩，面向过程的程序可以用一个dict表示：\n",
    "\n",
    "```\n",
    "std1 = { 'name': 'Michael', 'score': 98 }\n",
    "std2 = { 'name': 'Bob', 'score': 81 }\n",
    "```\n",
    "\n",
    "而处理学生成绩可以通过函数实现，比如打印学生的成绩：\n",
    "\n",
    "```\n",
    "def print_score(std):\n",
    "    print('%s: %s' % (std['name'], std['score']))\n",
    "```\n",
    "\n",
    "如果采用面向对象的程序设计思想，我们首选思考的不是程序的执行流程，而是`Student`这种数据类型应该被视为一个对象，这个对象拥有`name`和`score`这两个属性（Property）。如果要打印一个学生的成绩，首先必须创建出这个学生对应的对象，然后，给对象发一个`print_score`消息，让对象自己把自己的数据打印出来。\n",
    "\n",
    "```\n",
    "class Student(object):\n",
    "\n",
    "    def __init__(self, name, score):\n",
    "        self.name = name\n",
    "        self.score = score\n",
    "\n",
    "    def print_score(self):\n",
    "        print('%s: %s' % (self.name, self.score))\n",
    "```\n",
    "\n",
    "给对象发消息实际上就是调用对象对应的关联函数，我们称之为对象的方法（Method）。面向对象的程序写出来就像这样：\n",
    "\n",
    "```\n",
    "bart = Student('Bart Simpson', 59)\n",
    "lisa = Student('Lisa Simpson', 87)\n",
    "bart.print_score()\n",
    "lisa.print_score()\n",
    "```\n",
    "\n",
    "面向对象的设计思想是从自然界中来的，因为在自然界中，类（Class）和实例（Instance）的概念是很自然的。Class是一种抽象概念，比如我们定义的Class——Student，是指学生这个概念，而实例（Instance）则是一个个具体的Student，比如，Bart Simpson和Lisa Simpson是两个具体的Student。\n",
    "\n",
    "所以，面向对象的设计思想是抽象出Class，根据Class创建Instance。\n",
    "\n",
    "面向对象的抽象程度又比函数要高，因为一个Class既包含数据，又包含操作数据的方法。\n",
    "\n",
    "### 小结\n",
    "\n",
    "数据封装、继承和多态是面向对象的三大特点，我们后面会详细讲解。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## 类和实例\n",
    "\n",
    "面向对象最重要的概念就是类（Class）和实例（Instance），必须牢记类是抽象的模板，比如Student类，而实例是根据类创建出来的一个个具体的“对象”，每个对象都拥有相同的方法，但各自的数据可能不同。\n",
    "\n",
    "仍以Student类为例，在Python中，定义类是通过`class`关键字：\n",
    "\n",
    "```\n",
    "class Student(object):\n",
    "    pass\n",
    "```\n",
    "\n",
    "`class`后面紧接着是类名，即`Student`，类名通常是大写开头的单词，紧接着是`(object)`，表示该类是从哪个类继承下来的，继承的概念我们后面再讲，通常，如果没有合适的继承类，就使用`object`类，这是所有类最终都会继承的类。\n",
    "\n",
    "定义好了`Student`类，就可以根据`Student`类创建出`Student`的实例，创建实例是通过类名+()实现的：\n",
    "\n",
    "```\n",
    ">>> bart = Student()\n",
    ">>> bart\n",
    "<__main__.Student object at 0x10a67a590>\n",
    ">>> Student\n",
    "<class '__main__.Student'>\n",
    "```\n",
    "\n",
    "可以看到，变量`bart`指向的就是一个`Student`的实例，后面的`0x10a67a590`是内存地址，每个object的地址都不一样，而`Student`本身则是一个类。\n",
    "\n",
    "可以自由地给一个实例变量绑定属性，比如，给实例`bart`绑定一个`name`属性：\n",
    "\n",
    "```\n",
    ">>> bart.name = 'Bart Simpson'\n",
    ">>> bart.name\n",
    "'Bart Simpson'\n",
    "```\n",
    "\n",
    "由于类可以起到模板的作用，因此，可以在创建实例的时候，把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的`__init__`方法，在创建实例的时候，就把`name`，`score`等属性绑上去：\n",
    "\n",
    "```\n",
    "class Student(object):\n",
    "\n",
    "    def __init__(self, name, score):\n",
    "        self.name = name\n",
    "        self.score = score\n",
    "```\n",
    "\n",
    "注意：特殊方法“\\_\\_init\\_\\_”前后分别有两个下划线！！！\n",
    "\n",
    "注意到`__init__`方法的第一个参数永远是`self`，表示创建的实例本身，因此，在`__init__`方法内部，就可以把各种属性绑定到`self`，因为`self`就指向创建的实例本身。\n",
    "\n",
    "有了`__init__`方法，在创建实例的时候，就不能传入空的参数了，必须传入与`__init__`方法匹配的参数，但`self`不需要传，Python解释器自己会把实例变量传进去：\n",
    "\n",
    "```\n",
    ">>> bart = Student('Bart Simpson', 59)\n",
    ">>> bart.name\n",
    "'Bart Simpson'\n",
    ">>> bart.score\n",
    "59\n",
    "```\n",
    "\n",
    "和普通的函数相比，在类中定义的函数只有一点不同，就是第一个参数永远是实例变量`self`，并且，调用时，不用传递该参数。除此之外，类的方法和普通函数没有什么区别，所以，你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。\n",
    "\n",
    "### 数据封装\n",
    "\n",
    "面向对象编程的一个重要特点就是数据封装。在上面的`Student`类中，每个实例就拥有各自的`name`和`score`这些数据。我们可以通过函数来访问这些数据，比如打印一个学生的成绩：\n",
    "\n",
    "```\n",
    ">>> def print_score(std):\n",
    "...     print('%s: %s' % (std.name, std.score))\n",
    "...\n",
    ">>> print_score(bart)\n",
    "Bart Simpson: 59\n",
    "```\n",
    "\n",
    "但是，既然`Student`实例本身就拥有这些数据，要访问这些数据，就没有必要从外面的函数去访问，可以直接在`Student`类的内部定义访问数据的函数，这样，就把“数据”给封装起来了。这些封装数据的函数是和`Student`类本身是关联起来的，我们称之为类的方法：\n",
    "\n",
    "```\n",
    "class Student(object):\n",
    "\n",
    "    def __init__(self, name, score):\n",
    "        self.name = name\n",
    "        self.score = score\n",
    "\n",
    "    def print_score(self):\n",
    "        print('%s: %s' % (self.name, self.score))\n",
    "```\n",
    "\n",
    "要定义一个方法，除了第一个参数是`self`外，其他和普通函数一样。要调用一个方法，只需要在实例变量上直接调用，除了`self`不用传递，其他参数正常传入：\n",
    "\n",
    "```\n",
    ">>> bart.print_score()\n",
    "Bart Simpson: 59\n",
    "```\n",
    "\n",
    "这样一来，我们从外部看`Student`类，就只需要知道，创建实例需要给出`name`和`score`，而如何打印，都是在`Student`类的内部定义的，这些数据和逻辑被“封装”起来了，调用很容易，但却不用知道内部实现的细节。\n",
    "\n",
    "封装的另一个好处是可以给`Student`类增加新的方法，比如`get_grade`：\n",
    "\n",
    "```\n",
    "class Student(object):\n",
    "    ...\n",
    "\n",
    "    def get_grade(self):\n",
    "        if self.score >= 90:\n",
    "            return 'A'\n",
    "        elif self.score >= 60:\n",
    "            return 'B'\n",
    "        else:\n",
    "            return 'C'\n",
    "```\n",
    "\n",
    "同样的，`get_grade`方法可以直接在实例变量上调用，不需要知道内部实现细节：\n",
    "\n",
    "```\n",
    "# -*- coding: utf-8 -*-\n",
    "\n",
    "```\n",
    "\n",
    "```\n",
    "lisa = Student('Lisa', 99)\n",
    "bart = Student('Bart', 59)\n",
    "print(lisa.name, lisa.get_grade())\n",
    "print(bart.name, bart.get_grade())\n",
    "\n",
    "```\n",
    "\n",
    "### 小结\n",
    "\n",
    "类是创建实例的模板，而实例则是一个一个具体的对象，各个实例拥有的数据都互相独立，互不影响；\n",
    "\n",
    "方法就是与实例绑定的函数，和普通函数不同，方法可以直接访问实例的数据；\n",
    "\n",
    "通过在实例上调用方法，我们就直接操作了对象内部的数据，但无需知道方法内部的实现细节。\n",
    "\n",
    "和静态语言不同，Python允许对实例变量绑定任何数据，也就是说，对于两个实例变量，虽然它们都是同一个类的不同实例，但拥有的变量名称都可能不同：\n",
    "\n",
    "```\n",
    ">>> bart = Student('Bart Simpson', 59)\n",
    ">>> lisa = Student('Lisa Simpson', 87)\n",
    ">>> bart.age = 8\n",
    ">>> bart.age\n",
    "8\n",
    ">>> lisa.age\n",
    "Traceback (most recent call last):\n",
    "  File \"<stdin>\", line 1, in <module>\n",
    "AttributeError: 'Student' object has no attribute 'age'\n",
    "```\n",
    "\n",
    "### 参考源码\n",
    "\n",
    "[student.py](https://github.com/michaelliao/learn-python3/blob/master/samples/oop_basic/student.py)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## 访问限制\n",
    "\n",
    "面向对象最重要的概念就是类（Class）和实例（Instance），必须牢记类是抽象的模板，比如Student类，而实例是根据类创建出来的一个个具体的“对象”，每个对象都拥有相同的方法，但各自的数据可能不同。\n",
    "\n",
    "仍以Student类为例，在Python中，定义类是通过`class`关键字：\n",
    "\n",
    "```\n",
    "class Student(object):\n",
    "    pass\n",
    "```\n",
    "\n",
    "`class`后面紧接着是类名，即`Student`，类名通常是大写开头的单词，紧接着是`(object)`，表示该类是从哪个类继承下来的，继承的概念我们后面再讲，通常，如果没有合适的继承类，就使用`object`类，这是所有类最终都会继承的类。\n",
    "\n",
    "定义好了`Student`类，就可以根据`Student`类创建出`Student`的实例，创建实例是通过类名+()实现的：\n",
    "\n",
    "```\n",
    ">>> bart = Student()\n",
    ">>> bart\n",
    "<__main__.Student object at 0x10a67a590>\n",
    ">>> Student\n",
    "<class '__main__.Student'>\n",
    "```\n",
    "\n",
    "可以看到，变量`bart`指向的就是一个`Student`的实例，后面的`0x10a67a590`是内存地址，每个object的地址都不一样，而`Student`本身则是一个类。\n",
    "\n",
    "可以自由地给一个实例变量绑定属性，比如，给实例`bart`绑定一个`name`属性：\n",
    "\n",
    "```\n",
    ">>> bart.name = 'Bart Simpson'\n",
    ">>> bart.name\n",
    "'Bart Simpson'\n",
    "```\n",
    "\n",
    "由于类可以起到模板的作用，因此，可以在创建实例的时候，把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的`__init__`方法，在创建实例的时候，就把`name`，`score`等属性绑上去：\n",
    "\n",
    "```\n",
    "class Student(object):\n",
    "\n",
    "    def __init__(self, name, score):\n",
    "        self.name = name\n",
    "        self.score = score\n",
    "```\n",
    "\n",
    "注意：特殊方法“\\_\\_init\\_\\_”前后分别有两个下划线！！！\n",
    "\n",
    "注意到`__init__`方法的第一个参数永远是`self`，表示创建的实例本身，因此，在`__init__`方法内部，就可以把各种属性绑定到`self`，因为`self`就指向创建的实例本身。\n",
    "\n",
    "有了`__init__`方法，在创建实例的时候，就不能传入空的参数了，必须传入与`__init__`方法匹配的参数，但`self`不需要传，Python解释器自己会把实例变量传进去：\n",
    "\n",
    "```\n",
    ">>> bart = Student('Bart Simpson', 59)\n",
    ">>> bart.name\n",
    "'Bart Simpson'\n",
    ">>> bart.score\n",
    "59\n",
    "```\n",
    "\n",
    "和普通的函数相比，在类中定义的函数只有一点不同，就是第一个参数永远是实例变量`self`，并且，调用时，不用传递该参数。除此之外，类的方法和普通函数没有什么区别，所以，你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。\n",
    "\n",
    "### 数据封装\n",
    "\n",
    "面向对象编程的一个重要特点就是数据封装。在上面的`Student`类中，每个实例就拥有各自的`name`和`score`这些数据。我们可以通过函数来访问这些数据，比如打印一个学生的成绩：\n",
    "\n",
    "```\n",
    ">>> def print_score(std):\n",
    "...     print('%s: %s' % (std.name, std.score))\n",
    "...\n",
    ">>> print_score(bart)\n",
    "Bart Simpson: 59\n",
    "```\n",
    "\n",
    "但是，既然`Student`实例本身就拥有这些数据，要访问这些数据，就没有必要从外面的函数去访问，可以直接在`Student`类的内部定义访问数据的函数，这样，就把“数据”给封装起来了。这些封装数据的函数是和`Student`类本身是关联起来的，我们称之为类的方法：\n",
    "\n",
    "```\n",
    "class Student(object):\n",
    "\n",
    "    def __init__(self, name, score):\n",
    "        self.name = name\n",
    "        self.score = score\n",
    "\n",
    "    def print_score(self):\n",
    "        print('%s: %s' % (self.name, self.score))\n",
    "```\n",
    "\n",
    "要定义一个方法，除了第一个参数是`self`外，其他和普通函数一样。要调用一个方法，只需要在实例变量上直接调用，除了`self`不用传递，其他参数正常传入：\n",
    "\n",
    "```\n",
    ">>> bart.print_score()\n",
    "Bart Simpson: 59\n",
    "```\n",
    "\n",
    "这样一来，我们从外部看`Student`类，就只需要知道，创建实例需要给出`name`和`score`，而如何打印，都是在`Student`类的内部定义的，这些数据和逻辑被“封装”起来了，调用很容易，但却不用知道内部实现的细节。\n",
    "\n",
    "封装的另一个好处是可以给`Student`类增加新的方法，比如`get_grade`：\n",
    "\n",
    "```\n",
    "class Student(object):\n",
    "    ...\n",
    "\n",
    "    def get_grade(self):\n",
    "        if self.score >= 90:\n",
    "            return 'A'\n",
    "        elif self.score >= 60:\n",
    "            return 'B'\n",
    "        else:\n",
    "            return 'C'\n",
    "```\n",
    "\n",
    "同样的，`get_grade`方法可以直接在实例变量上调用，不需要知道内部实现细节：\n",
    "\n",
    "```\n",
    "# -*- coding: utf-8 -*-\n",
    "\n",
    "```\n",
    "\n",
    "```\n",
    "lisa = Student('Lisa', 99)\n",
    "bart = Student('Bart', 59)\n",
    "print(lisa.name, lisa.get_grade())\n",
    "print(bart.name, bart.get_grade())\n",
    "\n",
    "```\n",
    "\n",
    "### 小结\n",
    "\n",
    "类是创建实例的模板，而实例则是一个一个具体的对象，各个实例拥有的数据都互相独立，互不影响；\n",
    "\n",
    "方法就是与实例绑定的函数，和普通函数不同，方法可以直接访问实例的数据；\n",
    "\n",
    "通过在实例上调用方法，我们就直接操作了对象内部的数据，但无需知道方法内部的实现细节。\n",
    "\n",
    "和静态语言不同，Python允许对实例变量绑定任何数据，也就是说，对于两个实例变量，虽然它们都是同一个类的不同实例，但拥有的变量名称都可能不同：\n",
    "\n",
    "```\n",
    ">>> bart = Student('Bart Simpson', 59)\n",
    ">>> lisa = Student('Lisa Simpson', 87)\n",
    ">>> bart.age = 8\n",
    ">>> bart.age\n",
    "8\n",
    ">>> lisa.age\n",
    "Traceback (most recent call last):\n",
    "  File \"<stdin>\", line 1, in <module>\n",
    "AttributeError: 'Student' object has no attribute 'age'\n",
    "```\n",
    "\n",
    "### 参考源码\n",
    "\n",
    "[student.py](https://github.com/michaelliao/learn-python3/blob/master/samples/oop_basic/student.py)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## 继承和多态\n",
    "\n",
    "在OOP程序设计中，当我们定义一个class的时候，可以从某个现有的class继承，新的class称为子类（Subclass），而被继承的class称为基类、父类或超类（Base class、Super class）。\n",
    "\n",
    "比如，我们已经编写了一个名为`Animal`的class，有一个`run()`方法可以直接打印：\n",
    "\n",
    "```\n",
    "class Animal(object):\n",
    "    def run(self):\n",
    "        print('Animal is running...')\n",
    "```\n",
    "\n",
    "当我们需要编写`Dog`和`Cat`类时，就可以直接从`Animal`类继承：\n",
    "\n",
    "```\n",
    "class Dog(Animal):\n",
    "    pass\n",
    "\n",
    "class Cat(Animal):\n",
    "    pass\n",
    "```\n",
    "\n",
    "对于`Dog`来说，`Animal`就是它的父类，对于`Animal`来说，`Dog`就是它的子类。`Cat`和`Dog`类似。\n",
    "\n",
    "继承有什么好处？最大的好处是子类获得了父类的全部功能。由于`Animial`实现了`run()`方法，因此，`Dog`和`Cat`作为它的子类，什么事也没干，就自动拥有了`run()`方法：\n",
    "\n",
    "```\n",
    "dog = Dog()\n",
    "dog.run()\n",
    "\n",
    "cat = Cat()\n",
    "cat.run()\n",
    "```\n",
    "\n",
    "运行结果如下：\n",
    "\n",
    "```\n",
    "Animal is running...\n",
    "Animal is running...\n",
    "```\n",
    "\n",
    "当然，也可以对子类增加一些方法，比如Dog类：\n",
    "\n",
    "```\n",
    "class Dog(Animal):\n",
    "\n",
    "    def run(self):\n",
    "        print('Dog is running...')\n",
    "\n",
    "    def eat(self):\n",
    "        print('Eating meat...')\n",
    "```\n",
    "\n",
    "继承的第二个好处需要我们对代码做一点改进。你看到了，无论是`Dog`还是`Cat`，它们`run()`的时候，显示的都是`Animal is running...`，符合逻辑的做法是分别显示`Dog is running...`和`Cat is running...`，因此，对`Dog`和`Cat`类改进如下：\n",
    "\n",
    "```\n",
    "class Dog(Animal):\n",
    "\n",
    "    def run(self):\n",
    "        print('Dog is running...')\n",
    "\n",
    "class Cat(Animal):\n",
    "\n",
    "    def run(self):\n",
    "        print('Cat is running...')\n",
    "```\n",
    "\n",
    "再次运行，结果如下：\n",
    "\n",
    "```\n",
    "Dog is running...\n",
    "Cat is running...\n",
    "```\n",
    "\n",
    "当子类和父类都存在相同的`run()`方法时，我们说，子类的`run()`覆盖了父类的`run()`，在代码运行的时候，总是会调用子类的`run()`。这样，我们就获得了继承的另一个好处：多态。\n",
    "\n",
    "要理解什么是多态，我们首先要对数据类型再作一点说明。当我们定义一个class的时候，我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型，比如str、list、dict没什么两样：\n",
    "\n",
    "```\n",
    "a = list() # a是list类型\n",
    "b = Animal() # b是Animal类型\n",
    "c = Dog() # c是Dog类型\n",
    "```\n",
    "\n",
    "判断一个变量是否是某个类型可以用`isinstance()`判断：\n",
    "\n",
    "```\n",
    ">>> isinstance(a, list)\n",
    "True\n",
    ">>> isinstance(b, Animal)\n",
    "True\n",
    ">>> isinstance(c, Dog)\n",
    "True\n",
    "```\n",
    "\n",
    "看来`a`、`b`、`c`确实对应着`list`、`Animal`、`Dog`这3种类型。\n",
    "\n",
    "但是等等，试试：\n",
    "\n",
    "```\n",
    ">>> isinstance(c, Animal)\n",
    "True\n",
    "```\n",
    "\n",
    "看来`c`不仅仅是`Dog`，`c`还是`Animal`！\n",
    "\n",
    "不过仔细想想，这是有道理的，因为`Dog`是从`Animal`继承下来的，当我们创建了一个`Dog`的实例`c`时，我们认为`c`的数据类型是`Dog`没错，但`c`同时也是`Animal`也没错，`Dog`本来就是`Animal`的一种！\n",
    "\n",
    "所以，在继承关系中，如果一个实例的数据类型是某个子类，那它的数据类型也可以被看做是父类。但是，反过来就不行：\n",
    "\n",
    "```\n",
    ">>> b = Animal()\n",
    ">>> isinstance(b, Dog)\n",
    "False\n",
    "```\n",
    "\n",
    "`Dog`可以看成`Animal`，但`Animal`不可以看成`Dog`。\n",
    "\n",
    "要理解多态的好处，我们还需要再编写一个函数，这个函数接受一个`Animal`类型的变量：\n",
    "\n",
    "```\n",
    "def run_twice(animal):\n",
    "    animal.run()\n",
    "    animal.run()\n",
    "```\n",
    "\n",
    "当我们传入`Animal`的实例时，`run_twice()`就打印出：\n",
    "\n",
    "```\n",
    ">>> run_twice(Animal())\n",
    "Animal is running...\n",
    "Animal is running...\n",
    "```\n",
    "\n",
    "当我们传入`Dog`的实例时，`run_twice()`就打印出：\n",
    "\n",
    "```\n",
    ">>> run_twice(Dog())\n",
    "Dog is running...\n",
    "Dog is running...\n",
    "```\n",
    "\n",
    "当我们传入`Cat`的实例时，`run_twice()`就打印出：\n",
    "\n",
    "```\n",
    ">>> run_twice(Cat())\n",
    "Cat is running...\n",
    "Cat is running...\n",
    "```\n",
    "\n",
    "看上去没啥意思，但是仔细想想，现在，如果我们再定义一个`Tortoise`类型，也从`Animal`派生：\n",
    "\n",
    "```\n",
    "class Tortoise(Animal):\n",
    "    def run(self):\n",
    "        print('Tortoise is running slowly...')\n",
    "```\n",
    "\n",
    "当我们调用`run_twice()`时，传入`Tortoise`的实例：\n",
    "\n",
    "```\n",
    ">>> run_twice(Tortoise())\n",
    "Tortoise is running slowly...\n",
    "Tortoise is running slowly...\n",
    "```\n",
    "\n",
    "你会发现，新增一个`Animal`的子类，不必对`run_twice()`做任何修改，实际上，任何依赖`Animal`作为参数的函数或者方法都可以不加修改地正常运行，原因就在于多态。\n",
    "\n",
    "多态的好处就是，当我们需要传入`Dog`、`Cat`、`Tortoise`……时，我们只需要接收`Animal`类型就可以了，因为`Dog`、`Cat`、`Tortoise`……都是`Animal`类型，然后，按照`Animal`类型进行操作即可。由于`Animal`类型有`run()`方法，因此，传入的任意类型，只要是`Animal`类或者子类，就会自动调用实际类型的`run()`方法，这就是多态的意思：\n",
    "\n",
    "对于一个变量，我们只需要知道它是`Animal`类型，无需确切地知道它的子类型，就可以放心地调用`run()`方法，而具体调用的`run()`方法是作用在`Animal`、`Dog`、`Cat`还是`Tortoise`对象上，由运行时该对象的确切类型决定，这就是多态真正的威力：调用方只管调用，不管细节，而当我们新增一种`Animal`的子类时，只要确保`run()`方法编写正确，不用管原来的代码是如何调用的。这就是著名的“开闭”原则：\n",
    "\n",
    "对扩展开放：允许新增`Animal`子类；\n",
    "\n",
    "对修改封闭：不需要修改依赖`Animal`类型的`run_twice()`等函数。\n",
    "\n",
    "继承还可以一级一级地继承下来，就好比从爷爷到爸爸、再到儿子这样的关系。而任何类，最终都可以追溯到根类object，这些继承关系看上去就像一颗倒着的树。比如如下的继承树：\n",
    "\n",
    "```\n",
    "                ┌───────────────┐\n",
    "                │    object     │\n",
    "                └───────────────┘\n",
    "                        │\n",
    "           ┌────────────┴────────────┐\n",
    "           │                         │\n",
    "           ▼                         ▼\n",
    "    ┌─────────────┐           ┌─────────────┐\n",
    "    │   Animal    │           │    Plant    │\n",
    "    └─────────────┘           └─────────────┘\n",
    "           │                         │\n",
    "     ┌─────┴──────┐            ┌─────┴──────┐\n",
    "     │            │            │            │\n",
    "     ▼            ▼            ▼            ▼\n",
    "┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐\n",
    "│   Dog   │  │   Cat   │  │  Tree   │  │ Flower  │\n",
    "└─────────┘  └─────────┘  └─────────┘  └─────────┘\n",
    "```\n",
    "\n",
    "### 静态语言 vs 动态语言\n",
    "\n",
    "对于静态语言（例如Java）来说，如果需要传入`Animal`类型，则传入的对象必须是`Animal`类型或者它的子类，否则，将无法调用`run()`方法。\n",
    "\n",
    "对于Python这样的动态语言来说，则不一定需要传入`Animal`类型。我们只需要保证传入的对象有一个`run()`方法就可以了：\n",
    "\n",
    "```\n",
    "class Timer(object):\n",
    "    def run(self):\n",
    "        print('Start...')\n",
    "```\n",
    "\n",
    "这就是动态语言的“鸭子类型”，它并不要求严格的继承体系，一个对象只要“看起来像鸭子，走起路来像鸭子”，那它就可以被看做是鸭子。\n",
    "\n",
    "Python的“file-like object“就是一种鸭子类型。对真正的文件对象，它有一个`read()`方法，返回其内容。但是，许多对象，只要有`read()`方法，都被视为“file-like object“。许多函数接收的参数就是“file-like object“，你不一定要传入真正的文件对象，完全可以传入任何实现了`read()`方法的对象。\n",
    "\n",
    "### 小结\n",
    "\n",
    "继承可以把父类的所有功能都直接拿过来，这样就不必重零做起，子类只需要新增自己特有的方法，也可以把父类不适合的方法覆盖重写。\n",
    "\n",
    "动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。\n",
    "\n",
    "### 参考源码\n",
    "\n",
    "[animals.py](https://github.com/michaelliao/learn-python3/blob/master/samples/oop_basic/animals.py)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## 获取对象信息\n",
    "\n",
    "当我们拿到一个对象的引用时，如何知道这个对象是什么类型、有哪些方法呢？\n",
    "\n",
    "### 使用type()\n",
    "\n",
    "首先，我们来判断对象类型，使用`type()`函数：\n",
    "\n",
    "基本类型都可以用`type()`判断：\n",
    "\n",
    "```\n",
    ">>> type(123)\n",
    "<class 'int'>\n",
    ">>> type('str')\n",
    "<class 'str'>\n",
    ">>> type(None)\n",
    "<type(None) 'NoneType'>\n",
    "```\n",
    "\n",
    "如果一个变量指向函数或者类，也可以用`type()`判断：\n",
    "\n",
    "```\n",
    ">>> type(abs)\n",
    "<class 'builtin_function_or_method'>\n",
    ">>> type(a)\n",
    "<class '__main__.Animal'>\n",
    "```\n",
    "\n",
    "但是`type()`函数返回的是什么类型呢？它返回对应的Class类型。如果我们要在`if`语句中判断，就需要比较两个变量的type类型是否相同：\n",
    "\n",
    "```\n",
    ">>> type(123)==type(456)\n",
    "True\n",
    ">>> type(123)==int\n",
    "True\n",
    ">>> type('abc')==type('123')\n",
    "True\n",
    ">>> type('abc')==str\n",
    "True\n",
    ">>> type('abc')==type(123)\n",
    "False\n",
    "```\n",
    "\n",
    "判断基本数据类型可以直接写`int`，`str`等，但如果要判断一个对象是否是函数怎么办？可以使用`types`模块中定义的常量：\n",
    "\n",
    "```\n",
    ">>> import types\n",
    ">>> def fn():\n",
    "...     pass\n",
    "...\n",
    ">>> type(fn)==types.FunctionType\n",
    "True\n",
    ">>> type(abs)==types.BuiltinFunctionType\n",
    "True\n",
    ">>> type(lambda x: x)==types.LambdaType\n",
    "True\n",
    ">>> type((x for x in range(10)))==types.GeneratorType\n",
    "True\n",
    "```\n",
    "\n",
    "### 使用isinstance()\n",
    "\n",
    "对于class的继承关系来说，使用`type()`就很不方便。我们要判断class的类型，可以使用`isinstance()`函数。\n",
    "\n",
    "我们回顾上次的例子，如果继承关系是：\n",
    "\n",
    "```\n",
    "object -> Animal -> Dog -> Husky\n",
    "```\n",
    "\n",
    "那么，`isinstance()`就可以告诉我们，一个对象是否是某种类型。先创建3种类型的对象：\n",
    "\n",
    "```\n",
    ">>> a = Animal()\n",
    ">>> d = Dog()\n",
    ">>> h = Husky()\n",
    "```\n",
    "\n",
    "然后，判断：\n",
    "\n",
    "```\n",
    ">>> isinstance(h, Husky)\n",
    "True\n",
    "```\n",
    "\n",
    "没有问题，因为`h`变量指向的就是Husky对象。\n",
    "\n",
    "再判断：\n",
    "\n",
    "```\n",
    ">>> isinstance(h, Dog)\n",
    "True\n",
    "```\n",
    "\n",
    "`h`虽然自身是Husky类型，但由于Husky是从Dog继承下来的，所以，`h`也还是Dog类型。换句话说，`isinstance()`判断的是一个对象是否是该类型本身，或者位于该类型的父继承链上。\n",
    "\n",
    "因此，我们可以确信，`h`还是Animal类型：\n",
    "\n",
    "```\n",
    ">>> isinstance(h, Animal)\n",
    "True\n",
    "```\n",
    "\n",
    "同理，实际类型是Dog的`d`也是Animal类型：\n",
    "\n",
    "```\n",
    ">>> isinstance(d, Dog) and isinstance(d, Animal)\n",
    "True\n",
    "```\n",
    "\n",
    "但是，`d`不是Husky类型：\n",
    "\n",
    "```\n",
    ">>> isinstance(d, Husky)\n",
    "False\n",
    "```\n",
    "\n",
    "能用`type()`判断的基本类型也可以用`isinstance()`判断：\n",
    "\n",
    "```\n",
    ">>> isinstance('a', str)\n",
    "True\n",
    ">>> isinstance(123, int)\n",
    "True\n",
    ">>> isinstance(b'a', bytes)\n",
    "True\n",
    "```\n",
    "\n",
    "并且还可以判断一个变量是否是某些类型中的一种，比如下面的代码就可以判断是否是list或者tuple：\n",
    "\n",
    "```\n",
    ">>> isinstance([1, 2, 3], (list, tuple))\n",
    "True\n",
    ">>> isinstance((1, 2, 3), (list, tuple))\n",
    "True\n",
    "```\n",
    "\n",
    "总是优先使用isinstance()判断类型，可以将指定类型及其子类“一网打尽”。\n",
    "\n",
    "### 使用dir()\n",
    "\n",
    "如果要获得一个对象的所有属性和方法，可以使用`dir()`函数，它返回一个包含字符串的list，比如，获得一个str对象的所有属性和方法：\n",
    "\n",
    "```\n",
    ">>> dir('ABC')\n",
    "['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']\n",
    "```\n",
    "\n",
    "类似`__xxx__`的属性和方法在Python中都是有特殊用途的，比如`__len__`方法返回长度。在Python中，如果你调用`len()`函数试图获取一个对象的长度，实际上，在`len()`函数内部，它自动去调用该对象的`__len__()`方法，所以，下面的代码是等价的：\n",
    "\n",
    "```\n",
    ">>> len('ABC')\n",
    "3\n",
    ">>> 'ABC'.__len__()\n",
    "3\n",
    "```\n",
    "\n",
    "我们自己写的类，如果也想用`len(myObj)`的话，就自己写一个`__len__()`方法：\n",
    "\n",
    "```\n",
    ">>> class MyDog(object):\n",
    "...     def __len__(self):\n",
    "...         return 100\n",
    "...\n",
    ">>> dog = MyDog()\n",
    ">>> len(dog)\n",
    "100\n",
    "```\n",
    "\n",
    "剩下的都是普通属性或方法，比如`lower()`返回小写的字符串：\n",
    "\n",
    "```\n",
    ">>> 'ABC'.lower()\n",
    "'abc'\n",
    "```\n",
    "\n",
    "仅仅把属性和方法列出来是不够的，配合`getattr()`、`setattr()`以及`hasattr()`，我们可以直接操作一个对象的状态：\n",
    "\n",
    "```\n",
    ">>> class MyObject(object):\n",
    "...     def __init__(self):\n",
    "...         self.x = 9\n",
    "...     def power(self):\n",
    "...         return self.x * self.x\n",
    "...\n",
    ">>> obj = MyObject()\n",
    "```\n",
    "\n",
    "紧接着，可以测试该对象的属性：\n",
    "\n",
    "```\n",
    ">>> hasattr(obj, 'x') # 有属性'x'吗？\n",
    "True\n",
    ">>> obj.x\n",
    "9\n",
    ">>> hasattr(obj, 'y') # 有属性'y'吗？\n",
    "False\n",
    ">>> setattr(obj, 'y', 19) # 设置一个属性'y'\n",
    ">>> hasattr(obj, 'y') # 有属性'y'吗？\n",
    "True\n",
    ">>> getattr(obj, 'y') # 获取属性'y'\n",
    "19\n",
    ">>> obj.y # 获取属性'y'\n",
    "19\n",
    "```\n",
    "\n",
    "如果试图获取不存在的属性，会抛出AttributeError的错误：\n",
    "\n",
    "```\n",
    ">>> getattr(obj, 'z') # 获取属性'z'\n",
    "Traceback (most recent call last):\n",
    "  File \"<stdin>\", line 1, in <module>\n",
    "AttributeError: 'MyObject' object has no attribute 'z'\n",
    "```\n",
    "\n",
    "可以传入一个default参数，如果属性不存在，就返回默认值：\n",
    "\n",
    "```\n",
    ">>> getattr(obj, 'z', 404) # 获取属性'z'，如果不存在，返回默认值404\n",
    "404\n",
    "```\n",
    "\n",
    "也可以获得对象的方法：\n",
    "\n",
    "```\n",
    ">>> hasattr(obj, 'power') # 有属性'power'吗？\n",
    "True\n",
    ">>> getattr(obj, 'power') # 获取属性'power'\n",
    "<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>\n",
    ">>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn\n",
    ">>> fn # fn指向obj.power\n",
    "<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>\n",
    ">>> fn() # 调用fn()与调用obj.power()是一样的\n",
    "81\n",
    "```\n",
    "\n",
    "### 小结\n",
    "\n",
    "通过内置的一系列函数，我们可以对任意一个Python对象进行剖析，拿到其内部的数据。要注意的是，只有在不知道对象信息的时候，我们才会去获取对象信息。如果可以直接写：\n",
    "\n",
    "```\n",
    "sum = obj.x + obj.y\n",
    "```\n",
    "\n",
    "就不要写：\n",
    "\n",
    "```\n",
    "sum = getattr(obj, 'x') + getattr(obj, 'y')\n",
    "```\n",
    "\n",
    "一个正确的用法的例子如下：\n",
    "\n",
    "```\n",
    "def readImage(fp):\n",
    "    if hasattr(fp, 'read'):\n",
    "        return readData(fp)\n",
    "    return None\n",
    "```\n",
    "\n",
    "假设我们希望从文件流fp中读取图像，我们首先要判断该fp对象是否存在read方法，如果存在，则该对象是一个流，如果不存在，则无法读取。`hasattr()`就派上了用场。\n",
    "\n",
    "请注意，在Python这类动态语言中，根据鸭子类型，有`read()`方法，不代表该fp对象就是一个文件流，它也可能是网络流，也可能是内存中的一个字节流，但只要`read()`方法返回的是有效的图像数据，就不影响读取图像的功能。\n",
    "\n",
    "### 参考源码\n",
    "\n",
    "[get\\_type.py](https://github.com/michaelliao/learn-python3/blob/master/samples/oop_basic/get_type.py)\n",
    "\n",
    "[attrs.py](https://github.com/michaelliao/learn-python3/blob/master/samples/oop_basic/attrs.py)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## 实例属性和类属性\n",
    "\n",
    "由于Python是动态语言，根据类创建的实例可以任意绑定属性。\n",
    "\n",
    "给实例绑定属性的方法是通过实例变量，或者通过`self`变量：\n",
    "\n",
    "```\n",
    "class Student(object):\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "\n",
    "s = Student('Bob')\n",
    "s.score = 90\n",
    "```\n",
    "\n",
    "但是，如果`Student`类本身需要绑定一个属性呢？可以直接在class中定义属性，这种属性是类属性，归`Student`类所有：\n",
    "\n",
    "```\n",
    "class Student(object):\n",
    "    name = 'Student'\n",
    "```\n",
    "\n",
    "当我们定义了一个类属性后，这个属性虽然归类所有，但类的所有实例都可以访问到。来测试一下：\n",
    "\n",
    "```\n",
    ">>> class Student(object):\n",
    "...     name = 'Student'\n",
    "...\n",
    ">>> s = Student() # 创建实例s\n",
    ">>> print(s.name) # 打印name属性，因为实例并没有name属性，所以会继续查找class的name属性\n",
    "Student\n",
    ">>> print(Student.name) # 打印类的name属性\n",
    "Student\n",
    ">>> s.name = 'Michael' # 给实例绑定name属性\n",
    ">>> print(s.name) # 由于实例属性优先级比类属性高，因此，它会屏蔽掉类的name属性\n",
    "Michael\n",
    ">>> print(Student.name) # 但是类属性并未消失，用Student.name仍然可以访问\n",
    "Student\n",
    ">>> del s.name # 如果删除实例的name属性\n",
    ">>> print(s.name) # 再次调用s.name，由于实例的name属性没有找到，类的name属性就显示出来了\n",
    "Student\n",
    "```\n",
    "\n",
    "从上面的例子可以看出，在编写程序的时候，千万不要对实例属性和类属性使用相同的名字，因为相同名称的实例属性将屏蔽掉类属性，但是当你删除实例属性后，再使用相同的名称，访问到的将是类属性。\n",
    "\n",
    "### 练习\n",
    "\n",
    "为了统计学生人数，可以给Student类增加一个类属性，每创建一个实例，该属性自动增加："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "is_executing": true,
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# -*- coding: utf-8 -*-\n",
    "class Student(object):\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "\n",
    "\n",
    "# 测试:\n",
    "if Student.count != 0:\n",
    "    print('测试失败!')\n",
    "else:\n",
    "    bart = Student('Bart')\n",
    "    if Student.count != 1:\n",
    "        print('测试失败!')\n",
    "    else:\n",
    "        lisa = Student('Bart')\n",
    "        if Student.count != 2:\n",
    "            print('测试失败!')\n",
    "        else:\n",
    "            print('Students:', Student.count)\n",
    "            print('测试通过!')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "### 小结\n",
    "\n",
    "实例属性属于各个实例所有，互不干扰；\n",
    "\n",
    "类属性属于类所有，所有实例共享一个属性；\n",
    "\n",
    "不要对实例属性和类属性使用相同的名字，否则将产生难以发现的错误。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "is_executing": true,
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}